﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Castle.ActiveRecord;
using Castle.ActiveRecord.Queries;

namespace Matriculex.Dominio
{
    [ActiveRecord("oferecimento"), Serializable]
    public class Oferecimento : RemovableEntityWithId<Oferecimento>
    {
        // construtor vazio necessário para o ActiveRecord
        protected Oferecimento() { }

        public Oferecimento(Disciplina disciplina, PeriodoRematricula periodo, int vagas)
        {
            this.Vagas = vagas;
            this.Disciplina = disciplina;
            this.Periodo = periodo;
            this.AlunosMatriculados = new List<AlunoAberto>();
        }

        [BelongsTo]
        public Disciplina Disciplina { get; set; }

        [HasAndBelongsToMany(Table = "oferecimento_aluno", ColumnKey = "oferecimento_id", ColumnRef = "aluno_id")]
        private IList<AlunoAberto> AlunosMatriculados { get; set; }
        /// <summary>
        /// Obtém uma cópia read-only dos alunos matriculados neste oferecimento.
        /// </summary>
        public IList<AlunoAberto> Alunos
        {
            get
            {
                return new List<AlunoAberto>(AlunosMatriculados).AsReadOnly();
            }
        }
        [BelongsTo]
        public PeriodoRematricula Periodo { get; set; }
        [Property]
        public int Vagas { get; set; }

        public int VagasRestantes { get { return Vagas - Alunos.Count; } }


        /// <summary>
        /// Trava utilizada para garantir a sincronia quando uma matrícula for feita.
        /// </summary>
        private static readonly Object thisLock = new Object();

        /// <summary>
        /// Realiza a rematrícula do aluno neste oferecimento, e salva a entidade no
        /// banco de dados. São aceitos tanto alunos não matriculados, quanto alunos que
        /// já haviam se inscrito neste oferecimento. Lança DominioException se não
        /// houver mais vagas.
        /// </summary>
        /// <param name="aluno"></param>
        /// <exception cref="EsgotadoException">Não há mais vagas neste oferecimento</exception>
        /// <exception cref="ArgumentException">O aluno não pré-selecionou a disciplina oferecida</exception>
        internal void Matricula(AlunoAberto aluno)
        {
            if (!aluno.DisciplinasPreSelecionadas.Contains(this.Disciplina))
                throw new ArgumentException("O aluno não pré-selecionou esta disciplina");
            lock (thisLock)
            {
                // começa vendo se o aluno já não está matriculado; se tiver, não faz nada
                if (this.AlunosMatriculados.Contains(aluno))
                    return;
                // atualiza o número de vagas a partir do banco de dados
                this.Refresh();
                if (this.VagasRestantes == 0)
                    throw new EsgotadoException("Não há mais vagas na disciplina " + this.Disciplina.NomeCompleto);
                this.AlunosMatriculados.Add(aluno);
                this.SaveAndFlush();
            }
        }

        /// <summary>
        /// Cancela a matrícula deste aluno.
        /// </summary>
        /// <param name="a"></param>
        public void CancelaMatrícula(AlunoAberto aluno)
        {
            lock (thisLock)
            {
                if (!this.AlunosMatriculados.Contains(aluno))
                    return;
                this.AlunosMatriculados.Remove(aluno);
                this.SaveAndFlush();
            }
        }

        public void AdicionarAluno(AlunoAberto a)
        {
            AlunosMatriculados.Add(a);
        }
        public override string ToString()
        {
            return String.Format("{0} | {1} | {2}/{3} vagas restantes", this.Disciplina.Código, this.Periodo.Ciclo, this.VagasRestantes, this.Vagas);
        }

        public static Oferecimento[] FindAllByCiclo(Ciclo ciclo)
        {
            var query = new SimpleQuery<Oferecimento>(
            @"from Oferecimento o where o.Periodo.Ciclo.Ano = :ano and o.Periodo.Ciclo.Trimestre = :trimestre");
            query.SetParameter("ano", ciclo.Ano);
            query.SetParameter("trimestre", ciclo.Trimestre);

            return query.Execute().Where(o => o.Ativado).ToArray();
            //return FindAll().Where(o => o.Periodo.Ciclo.Equals(ciclo)).ToArray();
        }

        public static Oferecimento[] FindAllByPeriodoVigente()
        {
            var query = new SimpleQuery<Oferecimento>(
            @"from Oferecimento o where o.Periodo.Inicio < :now and :now < o.Periodo.Fim");
            query.SetParameter("now", DateTime.Now);

            return query.Execute().Where(o => o.Ativado).ToArray();
        }
    }
}
